home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1998 November: Tool Chest / Dev.CD Nov 98 TC.toast / Sample Code / Text / Inline Input for TextEdit / InlineInputSample / InlineInputSample.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-03-22  |  67.5 KB  |  2,353 lines  |  [TEXT/MPS ]

  1. /*
  2.     File:        InlineInputSample.c
  3.  
  4.     Contains:    C source file for InlineInputSample
  5.  
  6.     Copyright:    © 1989-1994 Apple Computer, Inc. All rights reserved.
  7.  
  8. */
  9.  
  10.  
  11. /* Segmentation strategy:
  12.  
  13.    There isn't any. Depending on which compiler you use, and whether you build for
  14.    Mac-OS 68K, A/UX, or PowerPC, segments may look different or not exist at all.
  15.    And with an application this small, segments don't really matter any more these
  16.    days. So we don't try to figure out segmentation and segment unloading strategies.
  17.    If you want to find out how to segment your application, look at some other
  18.    sample code.
  19. */
  20.  
  21.  
  22. /* SetPort strategy:
  23.  
  24.    Toolbox routines do not change the current port. In spite of this, in this
  25.    program we use a strategy of calling SetPort whenever we want to draw or
  26.    make calls which depend on the current port. This makes us less vulnerable
  27.    to bugs in other software which might alter the current port (such as the
  28.    bug (feature?) in many desk accessories which change the port on OpenDeskAcc).
  29.    Hopefully, this also makes the routines from this program more self-contained,
  30.    since they don't depend on the current port setting.
  31. */
  32.  
  33.  
  34. /* Clipboard strategy:
  35.  
  36.    Under styled TextEdit, TECut and TECopy will write both the text and associated
  37.    style information directly to the desk scrap as types 'TEXT' and 'styl'.
  38.    Instead of using TEToScrap and TEFromScrap, a new routine TEStylPaste, will 
  39.    transfer the text and style from the desk scrap to the document.
  40. */
  41.  
  42.  
  43. #if qInline
  44. #define qAppleEvents 1
  45. #endif
  46.  
  47.  
  48. #include <Limits.h>
  49. #include <Types.h>
  50. #include <QuickDraw.h>
  51. #include <Fonts.h>
  52. #include <Events.h>
  53. #include <Controls.h>
  54. #include <Windows.h>
  55. #include <Menus.h>
  56. #include <TextEdit.h>
  57. #include <Dialogs.h>
  58. #include <Desk.h>
  59. #include <Scrap.h>
  60. #include <ToolUtils.h>
  61. #include <Memory.h>
  62. #include <SegLoad.h>
  63. #include <Files.h>
  64. #include <OSUtils.h>
  65. #include <OSEvents.h>
  66. #include <Packages.h>
  67. #include <Traps.h>
  68. #include <Printing.h>
  69. #include <DiskInit.h>
  70. #if qAppleEvents
  71. #include <Errors.h>
  72. #include <GestaltEqu.h>
  73. #include <AppleEvents.h>
  74. #if qInline
  75. #include <TextServices.h>
  76. #include <Script.h>
  77. #include <TSMTE.h>
  78. #endif // qInline
  79. #endif // qAppleEvents
  80.  
  81. #include "InlineInputSample.h"
  82.  
  83.  
  84. // Constants
  85.  
  86. // top left corner of the disk initialization dialog
  87.  
  88. const short kDITop = 80;
  89. const short kDILeft = 112;
  90.  
  91. // the number of pixels we leave blank at the edge of the window
  92.  
  93. const short kTextMargin = 2;
  94.  
  95. // the maximum number of open documents at any one time. SetupMenus respects this
  96. // number, but the Apple event handlers don't.
  97.  
  98. const short kMaxOpenDocuments = 4;
  99.     
  100. // arbitrary number used to specify the width of the TERec's destination
  101. // rectangle so that word wrap and horizontal scrolling can be demonstrated
  102.  
  103. const short kMaxDocWidth = 576;
  104.     
  105. // the minimum dimension of a window for GrowWindow
  106.  
  107. const short kMinDocDim = 64;
  108.  
  109. // control contrlVis values to prevent or enable redrawing controls by
  110. // Control Manager routines such as SetCtlValue
  111.  
  112. const unsigned char kControlInvisible = 0;
  113. const unsigned char kControlVisible = 0xff;
  114.  
  115. // for calculating scroll bar positions and sizes
  116.  
  117. const short kScrollbarWidth = 16;
  118. const short kScrollbarAdjust = 15; // should be kScrollbarWidth - 1, but C is too stupid
  119. const short kScrollTweek = 2;
  120.     
  121. // ASCII code for delete character
  122.  
  123. const unsigned char kDelChar = 8;
  124.     
  125. // pixels to scroll when the button part of the horizontal scrollbar is pressed
  126.  
  127. const short kButtonScroll = 4;
  128.  
  129. // maximum text length we allow in a TERec; lower than 32767 to prevent errors
  130.  
  131. const short kMaxTELength = 32000;
  132.  
  133. // the SysEnvRec version we understand
  134.  
  135. const short kSysEnvironsVersion = 1;
  136.  
  137. // events mask for no events
  138.  
  139. const short kNoEventsMask = 0;
  140.  
  141. // the minimum heap size and minimal available heap space we require for running.
  142. // These are rough guesses; with styled TextEdit and printing you never really know.
  143.  
  144. const Size kMinHeap = 50 * 1024;
  145. const Size kMinSpace = 40 * 1024;
  146.  
  147. // values for setting up wide open rectangles and regions
  148.  
  149. const short kExtremeNeg = -32768;
  150. const short kExtremePos = 32767 -1; // required to address an old region bug
  151.  
  152. // extra security when pre-flighting edit commands
  153.  
  154. const short kTESlop = 1024;
  155.  
  156.  
  157. // Types
  158.  
  159. // A DocumentRecord contains the WindowRecord for one of our document windows,
  160. // as well as the TEHandle for the text we are editing. Other document fields
  161. // can be added to this record as needed. This is similar to how the
  162. // Window Manager and Dialog Manager add fields after the GrafPort.
  163.  
  164. typedef struct {
  165.     WindowRecord    docWindow;
  166.     TEHandle        docTE;
  167.     ControlHandle    docVScroll;
  168.     ControlHandle    docHScroll;
  169.     TEClickLoopUPP    docClick;
  170.     Boolean            modified;
  171. #if qInline
  172.     TSMTERecHandle    docTSMTERecHandle;
  173.     TSMDocumentID    docTSMDoc;
  174. #endif
  175. } DocumentRecord, *DocumentPeek;
  176.  
  177.  
  178. // Global Variables
  179.  
  180. // environment information that's set up during initialization
  181.  
  182. Boolean gHasWaitNextEvent;        // WaitNextEvent trap is available
  183. #if qAppleEvents
  184. Boolean gHasAppleEvents;        // Apple events are available, so we expect to get events from the Finder
  185. #if qInline
  186. Boolean gHasTextServices;        // Text Services Manager is available and should be used
  187. Boolean gHasTSMTE;                // Text Services for Text Edit are available and should be used
  188.                                 // gHasTSMTE can only be set if gHasTextServices.
  189. #endif // qInline
  190. #endif // qAppleEvents
  191.  
  192. // whether we are currently in the background. This accounts for major switches only.
  193.  
  194. Boolean gInBackground;
  195.  
  196. // the number of documents currently open
  197.  
  198. short gNumDocuments;
  199.  
  200. // print record shared among all documents ??? probably should be attached to documents instead
  201.  
  202. THPrint gPrinterRecord;
  203.  
  204. // universal procedure pointers
  205.  
  206. ControlActionUPP gHActionUPP;
  207. ControlActionUPP gVActionUPP;
  208. #if powerc
  209. TEClickLoopUPP gClickLoopUPP;
  210. #endif
  211. #if qAppleEvents
  212. AEEventHandlerUPP gHandleOAppUPP;
  213. AEEventHandlerUPP gHandleDocUPP;
  214. AEEventHandlerUPP gHandleQuitUPP;
  215. #if qInline
  216. TSMTEPreUpdateUPP gTSMTEPreUpdateUPP;
  217. TSMTEPostUpdateUPP gTSMTEPostUpdateUPP;
  218. #endif // qInline
  219. #endif // qAppleEvents
  220.  
  221. #if powerc
  222. // PowerPC libraries don't automatically define QuickDraw globals
  223.  
  224. QDGlobals qd;
  225. #endif
  226.  
  227. #if qAppleEvents
  228. // to indicate that Quit command or Apple event was successful
  229.  
  230. Boolean gQuitting;
  231.  
  232. #if qInline
  233. // variable to keep outside fontForce information
  234.  
  235. long gSavedFontForce;
  236.  
  237. #endif // qInline
  238. #endif // qAppleEvents
  239.  
  240.  
  241. // Routine Declarations
  242.  
  243. void AlertUser(short error);
  244. void EventLoop(void);
  245. void DoEvent(EventRecord *event);
  246. void AdjustCursor(Point mouse, RgnHandle region);
  247. void GetGlobalMouse(Point *mouse);
  248. void DoGrowWindow(WindowPtr window, EventRecord *event);
  249. void DoZoomWindow(WindowPtr window, short part);
  250. void ResizeWindow(WindowPtr window);
  251. void GetLocalUpdateRgn(WindowPtr window, RgnHandle localRgn);
  252. void DoUpdate(WindowPtr window);
  253. void DoActivate(WindowPtr window, Boolean becomingActive);
  254. void DoContentClick(WindowPtr window, EventRecord *event);
  255. void DoKeyDown(EventRecord *event);
  256. unsigned long GetSleep(void);
  257. void CommonAction(ControlHandle control, short *amount);
  258. pascal void VActionProc(ControlHandle control, short part);
  259. pascal void HActionProc(ControlHandle control, short part);
  260. void DoIdle(void);
  261. void DrawWindow(WindowPtr window);
  262. void AdjustMenus(void);
  263. void DoMenuCommand(long menuResult);
  264. void DoNew(void);
  265. Boolean DoCloseWindow(WindowPtr window);
  266. #if qAppleEvents
  267. static void PrepareToQuit(void);
  268. #else // qAppleEvents
  269. static void Terminate(void);
  270. #endif // qAppleEvents
  271. void Initialize(void);
  272. void BigBadError(short error);
  273. static void FailNilUPP(UniversalProcPtr theUPP);
  274. void GetTERect(WindowPtr window, Rect *teRect);
  275. void AdjustViewRect(TEHandle docTE);
  276. void AdjustTE(WindowPtr window);
  277. void AdjustHV(Boolean isVert, ControlHandle control, TEHandle docTE, Boolean canRedraw);
  278. void AdjustScrollValues(WindowPtr window, Boolean canRedraw);
  279. void AdjustScrollSizes(WindowPtr window);
  280. void AdjustScrollbars(WindowPtr window, Boolean needsResize);
  281. #if powerc
  282. pascal Boolean ClickLoopProc(TEPtr pTE);
  283. #else
  284. extern pascal void AsmClickLoopProc(void);
  285. #endif
  286. pascal void ClickLoopAddOn(void);
  287. pascal TEClickLoopUPP GetOldClickLoop(void);
  288. Boolean IsDocumentWindow(WindowPtr window);
  289. Boolean IsDAWindow(WindowPtr window);
  290. Boolean TrapAvailable(short theTrap);
  291. static void PrintText(TEHandle theText);
  292. #if qAppleEvents
  293. static void CheckAppleEvents(void);
  294. static OSErr InstallRequiredAppleEvents(void);
  295. static OSErr GotRequiredParameters(const AppleEvent *theAppleEvent);
  296. pascal OSErr HandleOAppEvent(const AppleEvent *theEvent, const AppleEvent *reply, long refCon);
  297. pascal OSErr HandleDocEvent(const AppleEvent *theEvent, const AppleEvent *reply, long refCon);
  298. pascal OSErr HandleQuitEvent(const AppleEvent *theEvent, const AppleEvent *reply, long refCon);
  299. #if qInline
  300. static void CheckForTextServices(void);
  301. static pascal void MyTSMTEPreUpdateProc(TEHandle textH, long refCon);
  302. static pascal void MyTSMTEPostUpdateProc(TEHandle textH, long fixLen, long inputAreaStart,
  303.             long inputAreaEnd, long pinStart, long pinEnd, long refCon);
  304. static void ExitApplication(void);
  305. #endif // qInline
  306. #endif // qAppleEvents
  307.  
  308.  
  309.  
  310. // Set up the whole world, including global variables, Toolbox managers.
  311. // If a problem occurs here, we alert the user and exit from the application.
  312.  
  313. void Initialize(void)
  314. {
  315.     EventRecord event;
  316.     short count;
  317.     SysEnvRec systemEnvironment;
  318.     long total, contig;
  319.     Handle menuBar;
  320.  
  321.     gInBackground = false;
  322. #if qAppleEvents
  323.     gQuitting = false;
  324. #endif // qAppleEvents
  325.  
  326.     InitGraf((Ptr) &qd.thePort);
  327.     InitFonts();
  328.     InitWindows();
  329.     InitMenus();
  330.     TEInit();
  331.     InitDialogs(nil);
  332.     InitCursor();
  333.  
  334.     // the following loop is necessary to allow the default button of our
  335.     // alert to be outlined. We use EventAvail instead of GetNextEvent so we
  336.     // don't lose events.
  337.     
  338.     for (count = 1; count <= 3; count++)
  339.         EventAvail(everyEvent, &event);
  340.     
  341.     // collect environment information
  342.  
  343.     // ignore the error returned from SysEnvirons; even if an error occurred,
  344.     // the SysEnvirons glue will fill in the SysEnvRec.
  345.      
  346.     SysEnvirons(kSysEnvironsVersion, &systemEnvironment);
  347.     
  348.     // make sure that the machine has at least 128K ROMs. If it doesn't, exit.
  349.     
  350.     if (systemEnvironment.machineType < 0)
  351.         BigBadError(eOldROM);
  352.     
  353.     // also, require at least system 6.0. The app should be able to run on system
  354.     // software 4.1 (where styled TextEdit was introduced) and later, but it's not really
  355.     // worth the trouble of testing on all those old systems anymore.
  356.     
  357.     if (systemEnvironment.systemVersion < 0x600)
  358.         BigBadError(eOldSystemSoftware);
  359.     
  360.     // It is better to first check the size of the application heap against a value
  361.     // that you have determined is the smallest heap the application can reasonably
  362.     // work in. This number should be derived by examining the size of the heap that
  363.     // is actually provided by MultiFinder when the minimum size requested is used.
  364.     // The check should be made because the preferred size can end up being set smaller
  365.     // than the minimum size by the user. This extra check acts to insure that your
  366.     // application is starting from a solid memory foundation.
  367.      
  368.     if ((long) GetApplLimit() - (long) ApplicationZone() < kMinHeap)
  369.         BigBadError(eSmallSize);
  370.     
  371.     // Next, make sure that enough memory is free for your application to run. It
  372.     // is possible for a situation to arise where the heap may have been of required
  373.     // size, but a large scrap was loaded which left too little memory. To check for
  374.     // this, call PurgeSpace and compare the result with a value that you have determined
  375.     // is the minimum amount of free memory your application needs at initialization.
  376.     // This number can be derived several different ways. One way that is fairly
  377.     // straightforward is to run the application in the minimum size configuration
  378.     // as described previously. Call PurgeSpace at initialization and examine the value
  379.     // returned. However, you should make sure that this result is not being modified
  380.     // by the scrap's presence. You can do that by calling ZeroScrap before calling
  381.     // PurgeSpace. Make sure to remove that call before shipping, though.
  382.     
  383.     // ZeroScrap();
  384.  
  385.     PurgeSpace(&total, &contig);
  386.     if (total < kMinSpace)
  387.         if (UnloadScrap() != noErr)
  388.             BigBadError(eNoMemory);
  389.         else
  390.         {
  391.             PurgeSpace(&total, &contig);
  392.             if (total < kMinSpace)
  393.                 BigBadError(eNoMemory);
  394.         };
  395.  
  396.     // The extra benefit to waiting until after the Toolbox Managers have been initialized
  397.     // to check memory is that we can now give the user an alert to tell him/her what
  398.     // happened. Although it is possible that the memory situation could be worsened by
  399.     // displaying an alert, MultiFinder would gracefully exit the application with
  400.     // an informative alert if memory became critical. Here we are acting more
  401.     // in a preventative manner to avoid future disaster from low-memory problems.
  402.  
  403.     // check for newer system services and set up our environment to make use of what's available.
  404.     
  405.     gHasWaitNextEvent = TrapAvailable(_WaitNextEvent);
  406.  
  407. #if qAppleEvents
  408.     CheckAppleEvents();
  409.     if (gHasAppleEvents)
  410.         (void) InstallRequiredAppleEvents();
  411. #if qInline
  412.  
  413.     CheckForTextServices();
  414.     
  415.     // this application uses TextEdit as the only text engine, and we don't support
  416.     // inline input without TSMTE. Therefore we call InitTSMAwareApplication only if
  417.     // TSMTE is available. A word processor that uses TextEdit only for dialogs
  418.     // and uses Text Services directly with the word processing engine would make this
  419.     // call depend on gHasTextServices.
  420.     
  421.     if (!(gHasTSMTE && InitTSMAwareApplication() == noErr))
  422.     {
  423.         // if this happens, just move on without text services
  424.         gHasTextServices = false;
  425.         gHasTSMTE = false;
  426.     };
  427.     
  428.     // get global fontForce flag, make sure it's off whenever we run
  429.     
  430.     gSavedFontForce = GetScriptManagerVariable(smFontForce);
  431.     (void) SetScriptManagerVariable(smFontForce, 0);
  432. #endif // qInline
  433. #endif // qAppleEvents
  434.  
  435.     // set up the menu bar and the menus that depend on the system environment
  436.     
  437.     menuBar = GetNewMBar(rMenuBar);    
  438.     if ( menuBar == nil )
  439.         BigBadError(eNoMemory);
  440.     SetMenuBar(menuBar);
  441.     DisposeHandle(menuBar);
  442.     AppendResMenu(GetMenuHandle(mApple), 'DRVR');    // build the Apple menu
  443.     AppendResMenu(GetMenuHandle(mFont), 'FONT');    // build the Font menu
  444.     DrawMenuBar();
  445.  
  446.     // we have no document open yet
  447.     
  448.     gNumDocuments = 0;
  449.  
  450.     // set up printer stuff - this will allow the default page setup parameters to be used,
  451.     // so if the user decides to print without using the Page Setup command everything will
  452.     // be OK
  453.     
  454.     gPrinterRecord = (THPrint) NewHandle(sizeof(TPrint));
  455.     if (gPrinterRecord != nil)
  456.     {
  457.         // if we got a print handle, initialize it to default values
  458.         PrOpen();
  459.         PrintDefault(gPrinterRecord);
  460.         PrClose();
  461.     };
  462.     
  463.     // initialize the universal procedure pointers that we need
  464. #if qAppleEvents
  465.     // (Apple event handler UPPs are set up in InstallRequiredAppleEvents)
  466. #endif // qAppleEvents
  467.     
  468.     gHActionUPP = NewControlActionProc(HActionProc);
  469.     FailNilUPP((UniversalProcPtr) gHActionUPP);
  470.     gVActionUPP = NewControlActionProc(VActionProc);
  471.     FailNilUPP((UniversalProcPtr) gVActionUPP);
  472. #if powerc
  473.     gClickLoopUPP = NewTEClickLoopProc(ClickLoopProc);
  474.     FailNilUPP((UniversalProcPtr) gClickLoopUPP);
  475. #endif
  476. #if qInline
  477.     if (gHasTSMTE)
  478.     {
  479.         gTSMTEPreUpdateUPP = NewTSMTEPreUpdateProc(MyTSMTEPreUpdateProc);
  480.         FailNilUPP((UniversalProcPtr) gTSMTEPreUpdateUPP);
  481.         gTSMTEPostUpdateUPP = NewTSMTEPostUpdateProc(MyTSMTEPostUpdateProc);
  482.         FailNilUPP((UniversalProcPtr) gTSMTEPostUpdateUPP);
  483.     };
  484. #endif // qInline
  485. }
  486.  
  487.  
  488. // report a fatal error to the user and exit from the application
  489.  
  490. void BigBadError(short error)
  491. {
  492.     AlertUser(error);
  493. #if qInline
  494.     ExitApplication();
  495. #else // qInline
  496.     ExitToShell();
  497. #endif // qInline
  498. }
  499.  
  500.  
  501. // check whether a valid UPP was allocated
  502.  
  503. static void FailNilUPP(UniversalProcPtr theUPP)
  504. {
  505.     if (theUPP == nil)
  506.         BigBadError(eNoMemory);
  507. }
  508.  
  509. #if qAppleEvents
  510.  
  511. // check to see if a given bit in a long word is set.
  512.  
  513. static Boolean BTst(long value, short bit)
  514. {
  515.     long mask = 1L << bit;
  516.     
  517.     return (value & mask) == mask;
  518. }
  519.  
  520. #endif // qAppleEvents
  521.  
  522. // check to see if a given trap is implemented. We follow IM VI-3-8.
  523.  
  524. Boolean TrapAvailable(short theTrap)
  525. {
  526.     TrapType theTrapType;
  527.     short numToolboxTraps;
  528.     
  529.     if ((theTrap & 0x0800) > 0)
  530.         theTrapType = ToolTrap;
  531.     else
  532.         theTrapType = OSTrap;
  533.  
  534.     if (theTrapType == ToolTrap)
  535.     {
  536.         theTrap = theTrap & 0x07ff;
  537.         if (NGetTrapAddress(_InitGraf, ToolTrap) == NGetTrapAddress(0xaa6e, ToolTrap))
  538.             numToolboxTraps = 0x0200;
  539.         else
  540.             numToolboxTraps = 0x0400;
  541.         if (theTrap >= numToolboxTraps)
  542.             theTrap = _Unimplemented;
  543.     };
  544.  
  545.     return (NGetTrapAddress(theTrap, theTrapType) != NGetTrapAddress(_Unimplemented, ToolTrap));
  546. }
  547.  
  548. #if qAppleEvents
  549.  
  550. static void CheckAppleEvents(void)
  551. {
  552.     long gestaltResponse;
  553.     
  554.     gHasAppleEvents = false;
  555.     
  556.     if (TrapAvailable(_Gestalt))
  557.     {
  558.         if (Gestalt(gestaltAppleEventsAttr, &gestaltResponse) == noErr)
  559.             gHasAppleEvents = BTst(gestaltResponse, gestaltAppleEventsPresent);
  560.     };
  561. }
  562.  
  563. #if qInline
  564.  
  565. // check whether the Text Services Manager and the extension for using Text Services with
  566. // TextEdit (TSMTE) are available, and sets gHasTextServices and gHasTSMTE accordingly.
  567.  
  568. static void CheckForTextServices(void)
  569. {
  570.     long gestaltResponse;
  571.     
  572.     gHasTextServices = false;        // unless proven otherwise
  573.     gHasTSMTE = false;                // unless proven otherwise
  574.     
  575.     if (TrapAvailable(_Gestalt))
  576.     {
  577.         if ((Gestalt(gestaltTSMgrVersion, &gestaltResponse) == noErr) && (gestaltResponse >= 1))
  578.         {
  579.             gHasTextServices = true;
  580.             if (Gestalt(gestaltTSMTEAttr, &gestaltResponse) == noErr)
  581.                 gHasTSMTE = BTst(gestaltResponse, gestaltTSMTEPresent);
  582.         };
  583.     };
  584. }
  585.  
  586. #endif // qInline
  587. #endif // qAppleEvents
  588.  
  589. void main(void)
  590. {
  591.     /*    If you have stack requirements that differ from the default,
  592.         then you could use SetApplLimit to increase StackSpace at 
  593.         this point, before calling MaxApplZone. */
  594.     MaxApplZone();                    /* expand the heap */
  595.  
  596.     Initialize();                    /* initialize the program */
  597. #if qAppleEvents
  598.     if (!gHasAppleEvents)
  599.         DoNew();
  600. #else // qAppleEvents
  601.     DoNew();
  602. #endif // qAppleEvents
  603.  
  604.     EventLoop();                    /* call the main event loop */
  605.  
  606. #if qAppleEvents
  607. #if qInline
  608.     ExitApplication();
  609. #else // qInline
  610.     ExitToShell();
  611. #endif // qInline
  612. #endif // qAppleEvents
  613. }
  614.  
  615. #if qInline
  616.  
  617. // IntlTSMEvent works around a bug in Kotoeri 1.0 (the Japanese input method),
  618. // which has been fixed in 1.1.1.
  619.  
  620. static Boolean IntlTSMEvent(EventRecord *event)
  621. {
  622.     short oldFont;
  623.     ScriptCode keyboardScript;
  624.     
  625.     // make sure we have a port and it's not the Window Manager port
  626.     if (qd.thePort != nil && FrontWindow() != nil)
  627.     {
  628.         oldFont = qd.thePort->txFont;
  629.         keyboardScript = GetScriptManagerVariable(smKeyScript);
  630.         if (FontToScript(oldFont) != keyboardScript)
  631.             TextFont(GetScriptVariable(keyboardScript, smScriptAppFond));
  632.     };
  633.     return TSMEvent(event);
  634. }
  635.  
  636. #endif // qInline
  637.  
  638. /* Get events forever, and handle them by calling DoEvent.
  639.    Also call AdjustCursor each time through the loop. */
  640.  
  641. void EventLoop(void)
  642. {
  643.     RgnHandle    cursorRgn;
  644.     Boolean        gotEvent;
  645.     EventRecord    event;
  646.     Point        mouse;
  647.  
  648.     cursorRgn = NewRgn();            /* we’ll pass WNE an empty region the 1st time thru */
  649. #if qAppleEvents
  650.     while (!gQuitting)
  651. #else // qAppleEvents
  652.     while (true) // loop forever, quit via ExitToShell
  653. #endif // qAppleEvents
  654.     {
  655. #if qInline
  656.         // set global fontForce flag so other apps don't get confused
  657.         (void) SetScriptManagerVariable(smFontForce, gSavedFontForce);
  658.         
  659. #endif // qInline
  660.         /* use WNE if it is available */
  661.         if ( gHasWaitNextEvent ) {
  662.             GetGlobalMouse(&mouse);
  663.             AdjustCursor(mouse, cursorRgn);
  664.             gotEvent = WaitNextEvent(everyEvent, &event, GetSleep(), cursorRgn);
  665.         }
  666.         else {
  667.             SystemTask();
  668.             gotEvent = GetNextEvent(everyEvent, &event);
  669.         };
  670.  
  671. #if qInline
  672.         // clear fontForce again so it doesn't upset our operations
  673.         gSavedFontForce = GetScriptManagerVariable(smFontForce);
  674.         (void) SetScriptManagerVariable(smFontForce, 0);
  675.         
  676.         // if we use inline input, we have to call IntlTSMEvent even with null events -
  677.         // some input methods rely on receiving them.
  678.         if (gHasTextServices && (gotEvent || event.what == nullEvent))
  679.             if (IntlTSMEvent(&event))
  680.                 gotEvent = false;
  681.         
  682. #endif // qInline
  683.         if ( gotEvent )
  684.         {
  685.             /* make sure we have the right cursor before handling the event */
  686.             AdjustCursor(event.where, cursorRgn);
  687.             DoEvent(&event);
  688.         }
  689.         else
  690.             DoIdle();                /* perform idle tasks when it’s not our event */
  691.         /*    If you are using modeless dialogs that have editText items,
  692.             you will want to call IsDialogEvent to give the caret a chance
  693.             to blink, even if WNE/GNE returned FALSE. However, check FrontWindow
  694.             for a non-NIL value before calling IsDialogEvent. */
  695.     };
  696. }
  697.  
  698.  
  699. /* Do the right thing for an event. Determine what kind of event it is, and call
  700.  the appropriate routines. */
  701.  
  702. void DoEvent(EventRecord *event)
  703. {
  704.     short        part, err;
  705.     WindowPtr    window;
  706.     char        key;
  707.     Point        aPoint;
  708. #if qInline
  709.     long        menuResult;
  710. #endif // qInline
  711.  
  712.     switch ( event->what ) {
  713.         case nullEvent:
  714.             /* we idle for null/mouse moved events ands for events which aren’t
  715.                 ours (see EventLoop) */
  716.             DoIdle();
  717.             break;
  718.         case mouseDown:
  719.             part = FindWindow(event->where, &window);
  720.             switch ( part ) {
  721.                 case inMenuBar:             /* process a mouse menu command (if any) */
  722.                     AdjustMenus();
  723. #if qInline
  724.                     menuResult = MenuSelect(event->where);
  725.                     if (!(gHasTextServices && TSMMenuSelect(menuResult)))
  726.                         DoMenuCommand(menuResult);
  727.                     HiliteMenu(0); // needed even if TSM or Script Manager handle the menu
  728. #else // qInline
  729.                     DoMenuCommand(MenuSelect(event->where));
  730. #endif // qInline
  731.                     break;
  732.                 case inSysWindow:           /* let the system handle the mouseDown */
  733.                     SystemClick(event, window);
  734.                     break;
  735.                 case inContent:
  736.                     if ( window != FrontWindow() ) {
  737.                         SelectWindow(window);
  738.                         AdjustMenus();
  739.                     } else
  740.                         DoContentClick(window, event);
  741.                     break;
  742.                 case inDrag:                /* pass screenBits.bounds to get all gDevices */
  743.                     DragWindow(window, event->where, &qd.screenBits.bounds);
  744.                     break;
  745.                 case inGoAway:
  746.                     if ( TrackGoAway(window, event->where) )
  747.                         DoCloseWindow(window); /* we don’t care if the user cancelled */
  748.                     break;
  749.                 case inGrow:
  750.                     DoGrowWindow(window, event);
  751.                     break;
  752.                 case inZoomIn:
  753.                 case inZoomOut:
  754.                 if ( TrackBox(window, event->where, part) )
  755.                         DoZoomWindow(window, part);
  756.                     break;
  757.             }
  758.             break;
  759.         case keyDown:
  760.         case autoKey:                       /* check for menukey equivalents */
  761.             key = event->message & charCodeMask;
  762.             if ( event->modifiers & cmdKey ) {    /* Command key down */
  763.                 if ( event->what == keyDown ) {
  764.                     AdjustMenus();            /* enable/disable/check menu items properly */
  765.                     DoMenuCommand(MenuKey(key));
  766.                 }
  767.             } else
  768.                 DoKeyDown(event);
  769.             break;
  770.         case activateEvt:
  771.             DoActivate((WindowPtr) event->message, (event->modifiers & activeFlag) != 0);
  772.             break;
  773.         case updateEvt:
  774.             DoUpdate((WindowPtr) event->message);
  775.             break;
  776.         /*    1.01 - It is not a bad idea to at least call DIBadMount in response
  777.             to a diskEvt, so that the user can format a floppy. */
  778.         case diskEvt:
  779.             if ( HiWord(event->message) != noErr ) {
  780.                 SetPt(&aPoint, kDILeft, kDITop);
  781.                 err = DIBadMount(aPoint, event->message);
  782.             }
  783.             break;
  784.         case osEvt:
  785.         /*    1.02 - must BitAND with 0x0FF to get only low byte */
  786.             switch ((event->message >> 24) & 0x0FF) {        /* high byte of message */
  787.                 case mouseMovedMessage:
  788.                     DoIdle();                    /* mouse-moved is also an idle event */
  789.                     break;
  790.                 case suspendResumeMessage:        /* suspend/resume is also an activate/deactivate */
  791.                     gInBackground = (event->message & resumeFlag) == 0;
  792.                     DoActivate(FrontWindow(), !gInBackground);
  793.                     break;
  794.             }
  795.             break;
  796. #if qAppleEvents
  797.         case kHighLevelEvent:
  798.             if (AEProcessAppleEvent(event) != noErr)
  799.                 ; // any ideas for error handling?
  800.             break;
  801. #endif // qAppleEvents
  802.     }
  803. } /*DoEvent*/
  804.  
  805.  
  806. /*    Change the cursor's shape, depending on its position. This also calculates the region
  807.     where the current cursor resides (for WaitNextEvent). When the mouse moves outside of
  808.     this region, an event is generated. If there is more to the event than just
  809.     “the mouse moved”, we get called before the event is processed to make sure
  810.     the cursor is the right one. In any (ahem) event, this is called again before we
  811.     fall back into WNE. */
  812.  
  813. void AdjustCursor(Point mouse, RgnHandle region)
  814. {
  815.     WindowPtr    window;
  816.     RgnHandle    arrowRgn;
  817.     RgnHandle    iBeamRgn;
  818.     Rect        iBeamRect;
  819.  
  820.     window = FrontWindow();    /* we only adjust the cursor when we are in front */
  821.     if ( (! gInBackground) && (! IsDAWindow(window)) ) {
  822.         /* calculate regions for different cursor shapes */
  823.         arrowRgn = NewRgn();
  824.         iBeamRgn = NewRgn();
  825.  
  826.         /* start arrowRgn wide open */
  827.         SetRectRgn(arrowRgn, kExtremeNeg, kExtremeNeg, kExtremePos, kExtremePos);
  828.  
  829.         /* calculate iBeamRgn */
  830.         if ( IsDocumentWindow(window) ) {
  831.             iBeamRect = (*((DocumentPeek) window)->docTE)->viewRect;
  832.             SetPort(window);    /* make a global version of the viewRect */
  833.             // ??? following two lines depend on Rect structure layout
  834.             LocalToGlobal((Point *) &(iBeamRect).top);
  835.             LocalToGlobal((Point *) &(iBeamRect).bottom);
  836.             RectRgn(iBeamRgn, &iBeamRect);
  837.             /* we temporarily change the port’s origin to “globalfy” the visRgn */
  838.             SetOrigin(-window->portBits.bounds.left, -window->portBits.bounds.top);
  839.             SectRgn(iBeamRgn, window->visRgn, iBeamRgn);
  840.             SetOrigin(0, 0);
  841.         }
  842.  
  843.         /* subtract other regions from arrowRgn */
  844.         DiffRgn(arrowRgn, iBeamRgn, arrowRgn);
  845.  
  846. #if qInline
  847.         // before we commit to anything, let's check whether some text service has a
  848.         // different idea
  849.         
  850.         if (!(gHasTextServices && SetTSMCursor(mouse)))
  851.         {
  852.             // change the cursor and the region parameter
  853.             if (PtInRgn(mouse, iBeamRgn))
  854.             {
  855.                 SetCursor(*GetCursor(iBeamCursor));
  856.                 CopyRgn(iBeamRgn, region);
  857.             }
  858.             else
  859.             {
  860.                 SetCursor(&qd.arrow);
  861.                 CopyRgn(arrowRgn, region);
  862.             };
  863.         };
  864.  
  865.         // and no matter how nice the region, with text services it cannot be bigger than
  866.         // a point. Yes, this defeats the purpose of all the calculations...
  867.         
  868.         if (gHasTextServices)
  869.             SetRectRgn(region, mouse.h, mouse.v, mouse.h, mouse.v);
  870. #else // qInline
  871.         // change the cursor and the region parameter
  872.         if (PtInRgn(mouse, iBeamRgn))
  873.         {
  874.             SetCursor(*GetCursor(iBeamCursor));
  875.             CopyRgn(iBeamRgn, region);
  876.         }
  877.         else
  878.         {
  879.             SetCursor(&qd.arrow);
  880.             CopyRgn(arrowRgn, region);
  881.         };
  882. #endif // qInline
  883.  
  884.         DisposeRgn(arrowRgn);
  885.         DisposeRgn(iBeamRgn);
  886.     }
  887. } /*AdjustCursor*/
  888.  
  889.  
  890. /*    Get the global coordinates of the mouse. When you call OSEventAvail
  891.     it will return either a pending event or a null event. In either case,
  892.     the where field of the event record will contain the current position
  893.     of the mouse in global coordinates and the modifiers field will reflect
  894.     the current state of the modifiers. Another way to get the global
  895.     coordinates is to call GetMouse and LocalToGlobal, but that requires
  896.     being sure that thePort is set to a valid port. */
  897.  
  898. void GetGlobalMouse(Point *mouse)
  899. {
  900.     EventRecord    event;
  901.     
  902.     OSEventAvail(kNoEventsMask, &event);    /* we aren't interested in any events */
  903.     *mouse = event.where;                /* just the mouse position */
  904. } /*GetGlobalMouse*/
  905.  
  906.  
  907. /*    Called when a mouseDown occurs in the grow box of an active window. In
  908.     order to eliminate any 'flicker', we want to invalidate only what is
  909.     necessary. Since ResizeWindow invalidates the whole portRect, we save
  910.     the old TE viewRect, intersect it with the new TE viewRect, and
  911.     remove the result from the update region. However, we must make sure
  912.     that any old update region that might have been around gets put back. */
  913.  
  914. void DoGrowWindow(WindowPtr window, EventRecord *event)
  915. {
  916.     long        growResult;
  917.     Rect        tempRect;
  918.     RgnHandle    tempRgn;
  919.     DocumentPeek doc;
  920.     
  921.     tempRect = qd.screenBits.bounds;                    /* set up limiting values */
  922.     tempRect.left = kMinDocDim;
  923.     tempRect.top = kMinDocDim;
  924.     growResult = GrowWindow(window, event->where, &tempRect);
  925.     /* see if it really changed size */
  926.     if ( growResult != 0 ) {
  927.         doc = (DocumentPeek) window;
  928.         tempRect = (*doc->docTE)->viewRect;                /* save old text box */
  929.         tempRgn = NewRgn();
  930.         GetLocalUpdateRgn(window, tempRgn);                /* get localized update region */
  931.         SizeWindow(window, LoWord(growResult), HiWord(growResult), true);
  932.         ResizeWindow(window);
  933.         /* calculate & validate the region that hasn’t changed so it won’t get redrawn */
  934.         SectRect(&tempRect, &(*doc->docTE)->viewRect, &tempRect);
  935.         ValidRect(&tempRect);                            /* take it out of update */
  936.         InvalRgn(tempRgn);                                /* put back any prior update */
  937.         DisposeRgn(tempRgn);
  938.     }
  939. } /* DoGrowWindow */
  940.  
  941.  
  942. /*     Called when a mouseClick occurs in the zoom box of an active window.
  943.     Everything has to get re-drawn here, so we don't mind that
  944.     ResizeWindow invalidates the whole portRect. */
  945.  
  946. void DoZoomWindow(WindowPtr window, short part)
  947. {
  948.     EraseRect(&window->portRect);
  949.     ZoomWindow(window, part, window == FrontWindow());
  950.     ResizeWindow(window);
  951. } /*  DoZoomWindow */
  952.  
  953.  
  954. /* Called when the window has been resized to fix up the controls and content. */
  955. void ResizeWindow(WindowPtr window)
  956. {
  957.     AdjustScrollbars(window, true);
  958.     AdjustTE(window);
  959.     InvalRect(&window->portRect);
  960. } /* ResizeWindow */
  961.  
  962.  
  963. /* Returns the update region in local coordinates */
  964. void GetLocalUpdateRgn(WindowPtr window, RgnHandle localRgn)
  965. {
  966.     CopyRgn(((WindowPeek) window)->updateRgn, localRgn);    /* save old update region */
  967.     OffsetRgn(localRgn, window->portBits.bounds.left, window->portBits.bounds.top);
  968. } /* GetLocalUpdateRgn */
  969.  
  970.  
  971. /*    This is called when an update event is received for a window.
  972.     It calls DrawWindow to draw the contents of an application window.
  973.     As an efficiency measure that does not have to be followed, it
  974.     calls the drawing routine only if the visRgn is non-empty. This
  975.     will handle situations where calculations for drawing or drawing
  976.     itself is very time-consuming. */
  977.  
  978. void DoUpdate(WindowPtr window)
  979. {
  980.     if ( IsDocumentWindow(window) ) {
  981.         BeginUpdate(window);                /* this sets up the visRgn */
  982.         if ( ! EmptyRgn(window->visRgn) )    /* draw if updating needs to be done */
  983.             DrawWindow(window);
  984.         EndUpdate(window);
  985.     }
  986. } /*DoUpdate*/
  987.  
  988.  
  989. /*    This is called when a window is activated or deactivated.
  990.     It calls TextEdit to deal with the selection. */
  991.  
  992. void DoActivate(WindowPtr window, Boolean becomingActive)
  993. {
  994.     RgnHandle    tempRgn, clipRgn;
  995.     Rect        growRect;
  996.     DocumentPeek doc;
  997.     
  998.     if ( IsDocumentWindow(window) ) {
  999.         doc = (DocumentPeek) window;
  1000.         if ( becomingActive ) {
  1001.             /*    since we don’t want TEActivate to draw a selection in an area where
  1002.                 we’re going to erase and redraw, we’ll clip out the update region
  1003.                 before calling it. */
  1004.             tempRgn = NewRgn();
  1005.             clipRgn = NewRgn();
  1006.             GetLocalUpdateRgn(window, tempRgn);            /* get localized update region */
  1007.             GetClip(clipRgn);
  1008.             DiffRgn(clipRgn, tempRgn, tempRgn);            /* subtract updateRgn from clipRgn */
  1009.             SetClip(tempRgn);
  1010.             TEActivate(doc->docTE);
  1011.             SetClip(clipRgn);                            /* restore the full-blown clipRgn */
  1012.             DisposeRgn(tempRgn);
  1013.             DisposeRgn(clipRgn);
  1014.             
  1015.             /* the controls must be redrawn on activation: */
  1016.             (*doc->docVScroll)->contrlVis = kControlVisible;
  1017.             (*doc->docHScroll)->contrlVis = kControlVisible;
  1018.             InvalRect(&(*doc->docVScroll)->contrlRect);
  1019.             InvalRect(&(*doc->docHScroll)->contrlRect);
  1020.             /* the growbox needs to be redrawn on activation: */
  1021.             growRect = window->portRect;
  1022.             /* adjust for the scrollbars */
  1023.             growRect.top = growRect.bottom - kScrollbarAdjust;
  1024.             growRect.left = growRect.right - kScrollbarAdjust;
  1025.             InvalRect(&growRect);
  1026. #if qInline
  1027.             if (doc->docTSMDoc != nil)
  1028.                 (void) ActivateTSMDocument(doc->docTSMDoc);
  1029. #endif // qInline
  1030.         }
  1031.         else
  1032.         {        
  1033. #if qInline
  1034.             if (doc->docTSMDoc != nil)
  1035.                 (void) DeactivateTSMDocument(doc->docTSMDoc);
  1036. #endif // qInline
  1037.             TEDeactivate(doc->docTE);
  1038.             /* the controls must be hidden on deactivation: */
  1039.             HideControl(doc->docVScroll);
  1040.             HideControl(doc->docHScroll);
  1041.             /* the growbox should be changed immediately on deactivation: */
  1042.             DrawGrowIcon(window);
  1043.         }
  1044.     }
  1045. } /*DoActivate*/
  1046.  
  1047.  
  1048. /*    This is called when a mouseDown occurs in the content of a window. */
  1049.  
  1050. void DoContentClick(WindowPtr window, EventRecord *event)
  1051. {
  1052.     Point        mouse;
  1053.     ControlHandle control;
  1054.     short        part, value;
  1055.     Boolean        shiftDown;
  1056.     DocumentPeek doc;
  1057.     Rect        teRect;
  1058.  
  1059.     if ( IsDocumentWindow(window) ) {
  1060.         SetPort(window);
  1061.         mouse = event->where;                            /* get the click position */
  1062.         GlobalToLocal(&mouse);
  1063.         doc = (DocumentPeek) window;
  1064.         /* see if we are in the viewRect. if so, we won’t check the controls */
  1065.         GetTERect(window, &teRect);
  1066.         if ( PtInRect(mouse, &teRect) ) {
  1067.             /* see if we need to extend the selection */
  1068.             shiftDown = (event->modifiers & shiftKey) != 0;    /* extend if Shift is down */
  1069.             TEClick(mouse, shiftDown, doc->docTE);
  1070.         } else {
  1071.             part = FindControl(mouse, window, &control);
  1072.             switch ( part ) {
  1073.                 case 0:                            /* do nothing for viewRect case */
  1074.                     break;
  1075.                 case inThumb:
  1076.                     value = GetControlValue(control);
  1077.                     part = TrackControl(control, mouse, nil);
  1078.                     if ( part != 0 ) {
  1079.                         value -= GetControlValue(control);
  1080.                         /* value now has CHANGE in value; if value changed, scroll */
  1081.                         if ( value != 0 )
  1082.                             if ( control == doc->docVScroll )
  1083.                                 TEScroll(0, value, doc->docTE);
  1084.                             else
  1085.                                 TEScroll(value, 0, doc->docTE);
  1086.                     }
  1087.                     break;
  1088.                 default:                        /* they clicked in an arrow, so track & scroll */
  1089.                     if ( control == doc->docVScroll )
  1090.                         value = TrackControl(control, mouse, gVActionUPP);
  1091.                     else
  1092.                         value = TrackControl(control, mouse, gHActionUPP);
  1093.                     break;
  1094.             }
  1095.         }
  1096.     }
  1097. } /*DoContentClick*/
  1098.  
  1099.  
  1100. /* This is called for any keyDown or autoKey events, except when the
  1101.  Command key is held down. It looks at the frontmost window to decide what
  1102.  to do with the key typed. */
  1103.  
  1104. void DoKeyDown(EventRecord *event)
  1105. {
  1106.     enum { minArrowKey = 28, maxArrowKey = 31 };
  1107.  
  1108.     WindowPtr    window;
  1109.     char        key;
  1110.     TEHandle    te;
  1111.  
  1112.     window = FrontWindow();
  1113.     if ( IsDocumentWindow(window) ) {
  1114.         te = ((DocumentPeek) window)->docTE;
  1115.         key = event->message & charCodeMask;
  1116.         /* we have a char. for our window; see if we are still below TextEdit’s
  1117.             limit for the number of characters (but deletes are always rad) */
  1118.         if ( key == kDelChar ||
  1119.                 (*te)->teLength - ((*te)->selEnd - (*te)->selStart) + 1 <
  1120.                 kMaxTELength )
  1121.         {
  1122.             TEKey(key, te);
  1123.             if ((key < minArrowKey) || (key > maxArrowKey)) // not a cursor key
  1124.                 ((DocumentPeek) window)->modified = true;
  1125.             AdjustScrollbars(window, false);
  1126.         }
  1127.         else
  1128.             AlertUser(eExceedChar);
  1129.     }
  1130. } /*DoKeyDown*/
  1131.  
  1132.  
  1133. /*    Calculate a sleep value for WaitNextEvent. This takes into account the things
  1134.     that DoIdle does with idle time. */
  1135.  
  1136. unsigned long GetSleep(void)
  1137. {
  1138.     long        sleep;
  1139.     WindowPtr    window;
  1140.     TEHandle    te;
  1141.  
  1142.     sleep = LONG_MAX;                        /* default value for sleep */
  1143.     if ( !gInBackground ) {
  1144.         window = FrontWindow();            /* and the front window is ours... */
  1145.         if ( IsDocumentWindow(window) ) {
  1146.             te = ((DocumentPeek) (window))->docTE;    /* and the selection is an insertion point... */
  1147.             if ( (*te)->selStart == (*te)->selEnd )
  1148.                 sleep = GetCaretTime();        /* blink time for the insertion point */
  1149.         }
  1150.     }
  1151.     return sleep;
  1152. } /*GetSleep*/
  1153.  
  1154.  
  1155. /*    Common algorithm for pinning the value of a control. It returns the actual amount
  1156.     the value of the control changed. Note the pinning is done for the sake of returning
  1157.     the amount the control value changed. */
  1158.  
  1159. void CommonAction(ControlHandle control, short *amount)
  1160. {
  1161.     short        value, max;
  1162.     
  1163.     value = GetControlValue(control);    /* get current value */
  1164.     max = GetControlMaximum(control);        /* and maximum value */
  1165.     *amount = value - *amount;
  1166.     if ( *amount < 0 )
  1167.         *amount = 0;
  1168.     else if ( *amount > max )
  1169.         *amount = max;
  1170.     SetControlValue(control, *amount);
  1171.     *amount = value - *amount;        /* calculate the real change */
  1172. } /* CommonAction */
  1173.  
  1174.  
  1175. /* Determines how much to change the value of the vertical scrollbar by and how
  1176.     much to scroll the TE record. */
  1177.  
  1178. pascal void VActionProc(ControlHandle control, short part)
  1179. {
  1180.     short        amount;
  1181.     WindowPtr    window;
  1182.     TEPtr        te;
  1183.     
  1184.     if ( part != 0 ) {                /* if it was actually in the control */
  1185.         window = (*control)->contrlOwner;
  1186.         te = *((DocumentPeek) window)->docTE;
  1187.         switch ( part ) {
  1188.             case inUpButton:
  1189.             case inDownButton:
  1190.                 amount = 24;
  1191.                 break;
  1192.             case inPageUp:            /* one page */
  1193.             case inPageDown:
  1194.                 amount = te->viewRect.bottom - te->viewRect.top;
  1195.                 break;
  1196.         }
  1197.         if ( (part == inDownButton) || (part == inPageDown) )
  1198.             amount = -amount;        /* reverse direction for a downer */
  1199.         CommonAction(control, &amount);
  1200.         if ( amount != 0 )
  1201.             TEScroll(0, amount, ((DocumentPeek) window)->docTE);
  1202.     }
  1203. } /* VActionProc */
  1204.  
  1205.  
  1206. /* Determines how much to change the value of the horizontal scrollbar by and how
  1207. much to scroll the TE record. */
  1208.  
  1209. pascal void HActionProc(ControlHandle control, short part)
  1210. {
  1211.     short        amount;
  1212.     WindowPtr    window;
  1213.     TEPtr        te;
  1214.     
  1215.     if ( part != 0 ) {
  1216.         window = (*control)->contrlOwner;
  1217.         te = *((DocumentPeek) window)->docTE;
  1218.         switch ( part ) {
  1219.             case inUpButton:
  1220.             case inDownButton:        /* a few pixels */
  1221.                 amount = kButtonScroll;
  1222.                 break;
  1223.             case inPageUp:            /* a page */
  1224.             case inPageDown:
  1225.                 amount = te->viewRect.right - te->viewRect.left;
  1226.                 break;
  1227.         }
  1228.         if ( (part == inDownButton) || (part == inPageDown) )
  1229.             amount = -amount;        /* reverse direction */
  1230.         CommonAction(control, &amount);
  1231.         if ( amount != 0 )
  1232.             TEScroll(amount, 0, ((DocumentPeek) window)->docTE);
  1233.     }
  1234. } /* VActionProc */
  1235.  
  1236.  
  1237. /* This is called whenever we get a null event et al.
  1238.  It takes care of necessary periodic actions. For this program, it calls TEIdle. */
  1239.  
  1240. void DoIdle(void)
  1241. {
  1242.     WindowPtr    window;
  1243.  
  1244.     window = FrontWindow();
  1245.     if ( IsDocumentWindow(window) )
  1246.         TEIdle(((DocumentPeek) window)->docTE);
  1247. } /*DoIdle*/
  1248.  
  1249.  
  1250. /* Draw the contents of an application window. */
  1251.  
  1252. void DrawWindow(WindowPtr window)
  1253. {
  1254.     SetPort(window);
  1255.     EraseRect(&window->portRect);
  1256.     DrawControls(window);
  1257.     DrawGrowIcon(window);
  1258.     TEUpdate(&window->portRect, ((DocumentPeek) window)->docTE);
  1259. } /*DrawWindow*/
  1260.  
  1261.  
  1262. // menu handling utilities
  1263.  
  1264. static void DisableMenu(MenuHandle menu)
  1265. {
  1266.     DisableItem(menu, 0);
  1267. }
  1268.  
  1269.  
  1270. static void EnableMenu(MenuHandle menu)
  1271. {
  1272.     EnableItem(menu, 0);
  1273. }
  1274.  
  1275. static void RemoveMenuCheckMarks(MenuHandle menu)
  1276. {
  1277.     short item;
  1278.  
  1279.     for (item = 1; item <= CountMItems(menu); item++)
  1280.     {
  1281.         CheckItem(menu, item, false);
  1282.     };
  1283. }
  1284.  
  1285. static void RemoveMenuStyles(MenuHandle menu)
  1286. {
  1287.     short item;
  1288.  
  1289.     for (item = 1; item <= CountMItems(menu); item++)
  1290.     {
  1291.         SetItemStyle(menu, item, normal);
  1292.     };
  1293. }
  1294.  
  1295. static void EnableItemIf(MenuHandle menu, short item, Boolean condition)
  1296. {
  1297.     if (condition)
  1298.         EnableItem(menu, item);
  1299.     else
  1300.         DisableItem(menu, item);
  1301. }
  1302.  
  1303.  
  1304. // enable and disable menus based on the current state.
  1305. // In general, we set up the menu items only before calling MenuSelect or MenuKey,
  1306. // since these are the only times that a menu item can be selected. However, we
  1307. // also have to set up the menus whenever the front window changes because
  1308. // we enable and disable some menu titles depending on the front window.
  1309.  
  1310. void AdjustMenus(void)
  1311. {
  1312.     WindowPtr    window;
  1313.     Boolean        frontIsDAWindow;
  1314.     Boolean        frontIsDocWindow;
  1315.     Boolean        docHasSelection;
  1316.     Boolean        clipboardHasText;
  1317.     Boolean        haveContinuousFont;
  1318.     MenuHandle    menu;
  1319.     long        offset;
  1320.     TEHandle    te;
  1321.     TextStyle    theTextStyle;
  1322.     short        theFont;
  1323.     short        mode;
  1324.     short        item;
  1325.     
  1326.  
  1327.     // gather some state information that we'll need to decide which menu items to enable
  1328.     window = FrontWindow();
  1329.     frontIsDAWindow = (window != nil) && IsDAWindow(window);
  1330.     frontIsDocWindow = (window != nil) && IsDocumentWindow(window);
  1331.     if (frontIsDocWindow)
  1332.     {
  1333.         te = ((DocumentPeek) window)->docTE;
  1334.         docHasSelection = (*te)->selStart < (*te)->selEnd;
  1335.     }
  1336.     else
  1337.         docHasSelection = false;
  1338.     clipboardHasText = GetScrap(nil, 'TEXT', &offset)  > 0;
  1339.         // note that TEGetScrapLength works for the private TextEdit scrap only, which
  1340.         // is not used by styled TextEdit
  1341.     haveContinuousFont = false; // will really be set when setting up font menu
  1342.     
  1343.     // Apple menu is always enabled
  1344.     
  1345.     // File menu
  1346.     menu = GetMenuHandle(mFile);
  1347.     EnableItemIf(menu, iNew, gNumDocuments < kMaxOpenDocuments);
  1348.     EnableItemIf(menu, iClose, window != nil);
  1349.     EnableItemIf(menu, iPageSetup, frontIsDocWindow);
  1350.     EnableItemIf(menu, iPrint, frontIsDocWindow);
  1351.     EnableItem(menu, iQuit);
  1352.  
  1353.     menu = GetMenuHandle(mEdit);
  1354.     EnableItemIf(menu, iUndo, frontIsDAWindow); // can't handle Undo for documents yet
  1355.     EnableItemIf(menu, iCut, frontIsDAWindow || docHasSelection);
  1356.     EnableItemIf(menu, iCopy, frontIsDAWindow || docHasSelection);
  1357.     EnableItemIf(menu, iPaste, frontIsDAWindow || (frontIsDocWindow && clipboardHasText));
  1358.     EnableItemIf(menu, iClear, frontIsDAWindow || docHasSelection);
  1359.     EnableItemIf(menu, iSelectAll, frontIsDocWindow);
  1360.     
  1361.     menu = GetMenuHandle(mFont);
  1362.     RemoveMenuCheckMarks(menu);
  1363.     if (frontIsDocWindow)
  1364.     {
  1365.         EnableMenu(menu);
  1366.  
  1367.         mode = doFont;
  1368.         if (TEContinuousStyle(&mode, &theTextStyle, ((DocumentPeek) window)->docTE))
  1369.         {
  1370.             Str255 theName, itemName;
  1371.             short itemCount;
  1372.             
  1373.             GetFontName(theTextStyle.tsFont, theName);
  1374.             itemCount = CountMItems(menu);
  1375.             for (item = 1; item <= itemCount; item++)
  1376.             {
  1377.                 GetMenuItemText(menu, item, itemName);
  1378.                 if (EqualString(theName, itemName, true, true))
  1379.                 {
  1380.                     CheckItem(menu, item, true);
  1381.                     break;
  1382.                 };
  1383.             };
  1384.             
  1385.             haveContinuousFont = true;
  1386.             theFont = theTextStyle.tsFont;
  1387.         };
  1388.     }
  1389.     else
  1390.     {
  1391.         DisableMenu(menu);
  1392.     };
  1393.     
  1394.     menu = GetMenuHandle (mFontSize);
  1395.     RemoveMenuCheckMarks(menu);
  1396.     RemoveMenuStyles(menu);
  1397.     if (frontIsDocWindow)
  1398.     {
  1399.         EnableMenu(menu);
  1400.  
  1401.         mode = doSize;
  1402.         if (TEContinuousStyle(&mode, &theTextStyle, ((DocumentPeek) window)->docTE))
  1403.         {        
  1404.             switch (theTextStyle.tsSize)
  1405.             {
  1406.                 case 9: item = iNine; break;
  1407.                 case 10: item = iTen; break;
  1408.                 case 12: item = iTwelve; break;
  1409.                 case 14: item = iFourteen; break;
  1410.                 case 18: item = iEighteen; break;
  1411.                 case 24: item = iTwentyFour; break;
  1412.             };
  1413.             CheckItem(menu, item, true);
  1414.         };
  1415.  
  1416.         if (haveContinuousFont)
  1417.         {
  1418.             if (RealFont(theFont, 9))
  1419.                 SetItemStyle(menu, iNine, outline);
  1420.             if (RealFont(theFont, 10))
  1421.                 SetItemStyle(menu, iTen, outline);
  1422.             if (RealFont(theFont, 12))
  1423.                 SetItemStyle(menu, iTwelve, outline);
  1424.             if (RealFont(theFont, 14))
  1425.                 SetItemStyle(menu, iFourteen, outline);
  1426.             if (RealFont(theFont, 18))
  1427.                 SetItemStyle(menu, iEighteen, outline);
  1428.             if (RealFont(theFont, 24))
  1429.                 SetItemStyle(menu, iTwentyFour, outline);
  1430.         };
  1431.     }
  1432.     else
  1433.     {
  1434.         DisableMenu(menu);
  1435.     };
  1436.     
  1437.     menu = GetMenuHandle (mStyle);
  1438.     RemoveMenuCheckMarks(menu);
  1439.     if (frontIsDocWindow)
  1440.     {
  1441.         EnableMenu(menu);
  1442.  
  1443.         mode = doFace;
  1444.         if (TEContinuousStyle(&mode, &theTextStyle, ((DocumentPeek) window)->docTE))
  1445.         {
  1446.             CheckItem(menu, iPlain, theTextStyle.tsFace == normal);
  1447.             CheckItem(menu, iBold, (theTextStyle.tsFace & bold) == bold);
  1448.             CheckItem(menu, iItalic, (theTextStyle.tsFace & italic) == italic);
  1449.             CheckItem(menu, iUnderline, (theTextStyle.tsFace & underline) == underline);
  1450.             CheckItem(menu, iOutline, (theTextStyle.tsFace & outline) == outline);
  1451.             CheckItem(menu, iShadow, (theTextStyle.tsFace & shadow) == shadow);
  1452.         };        
  1453.     }
  1454.     else
  1455.     {
  1456.         DisableMenu(menu);
  1457.     };
  1458.     
  1459.     DrawMenuBar();
  1460. }
  1461.  
  1462.  
  1463. /*    This is called when an item is chosen from the menu bar (after calling
  1464.     MenuSelect or MenuKey). It does the right thing for each command. */
  1465.  
  1466. void DoMenuCommand(long menuResult)
  1467. {
  1468.     short        menuID, menuItem;
  1469.     short        itemHit, daRefNum;
  1470.     Str255        daName;
  1471.     OSErr        saveErr;
  1472.     TEHandle    te;
  1473.     WindowPtr    window;
  1474.     long        scrapLength;
  1475.     long        offset;
  1476.     Handle        aHandle;
  1477.     long        oldSize, newSize;
  1478.     long        total, contig;
  1479.     TextStyle    theTextStyle;
  1480.     Str255        theFontName;
  1481.     short        theFontID;
  1482.     short        theFontSize;
  1483.     DocumentPeek theDocument;
  1484. #if qInline
  1485.     TSMDocumentID tsmDoc;
  1486. #endif // qInline
  1487.  
  1488.     window = FrontWindow();
  1489.     menuID = HiWord(menuResult);
  1490.     menuItem = LoWord(menuResult);
  1491.  
  1492. #if qInline
  1493.     if (menuID == 0)
  1494.         // no real menu command, so we don't want to confirm inline input text
  1495.         return;
  1496.     
  1497. #endif // qInline
  1498.     if (IsDocumentWindow(window))
  1499.     {
  1500.         theDocument = (DocumentPeek) window;
  1501.         te = theDocument->docTE;
  1502. #if qInline
  1503.  
  1504.         // for any real menu command, we first confirm inline input text if there is any.
  1505.         // This is somewhat controversial - it can be argued that for some commands, e.g.
  1506.         // opening a new document, it's not necessary to confirm the text. A user might do
  1507.         // some editing in the new document, then come back and continue with the inline
  1508.         // input. Then again, this might conflict with the Undo model you use. Test with
  1509.         // real users, then use your best judgment.
  1510.  
  1511.         tsmDoc = theDocument->docTSMDoc;
  1512.         if (tsmDoc != nil)
  1513.             (void) FixTSMDocument(tsmDoc);
  1514. #endif // qInline
  1515.     };
  1516.     
  1517.     switch ( menuID ) {
  1518.         case mApple:
  1519.             switch ( menuItem ) {
  1520.                 case iAbout:        /* bring up alert for About */
  1521.                     itemHit = Alert(rAboutAlert, nil);
  1522.                     break;
  1523.                 default:            /* all non-About items in this menu are DAs et al */
  1524.                     /* type Str255 is an array in MPW 3 */
  1525.                     GetMenuItemText(GetMenuHandle(mApple), menuItem, daName);
  1526.                     daRefNum = OpenDeskAcc(daName);
  1527.                     AdjustMenus();
  1528.                     break;
  1529.             }
  1530.             break;
  1531.         case mFile:
  1532.             switch ( menuItem ) {
  1533.                 case iNew:
  1534.                     DoNew();
  1535.                     break;
  1536.                 case iClose:
  1537.                     (void) DoCloseWindow(FrontWindow());            /* ignore the result */
  1538.                     break;
  1539.                 case iPageSetup:
  1540.                     PrOpen();
  1541.                     if (PrError() == noErr)
  1542.                         (void) PrStlDialog(gPrinterRecord);
  1543.                     PrClose();
  1544.                     break;
  1545.                 case iPrint:
  1546.                     PrintText(te);
  1547.                     break;
  1548.                 case iQuit:
  1549. #if qAppleEvents
  1550.                     PrepareToQuit();
  1551. #else // qAppleEvents
  1552.                     Terminate();
  1553. #endif // qAppleEvents
  1554.                     break;
  1555.             }
  1556.             break;
  1557.         case mEdit:                    /* call SystemEdit for DA editing & MultiFinder */
  1558.             if ( !SystemEdit(menuItem-1) ) {
  1559.                 switch ( menuItem ) {
  1560.                     case iCut:
  1561.                         if ( ZeroScrap() == noErr )
  1562.                         {
  1563.                             PurgeSpace(&total, &contig);
  1564.                             if ((*te)->selEnd - (*te)->selStart + kTESlop > contig)
  1565.                                 AlertUser(eNoSpaceCut);
  1566.                             else
  1567.                             {
  1568.                                 TECut(te);
  1569.                                 theDocument->modified = true;
  1570.                             };
  1571.                         };
  1572.                         break;
  1573.                     case iCopy:
  1574.                         if ( ZeroScrap() == noErr )
  1575.                             TECopy(te);
  1576.                         break;
  1577.                     case iPaste:
  1578.                         scrapLength = GetScrap(nil, 'TEXT', &offset);
  1579.                         if ( scrapLength + ((*te)->teLength -
  1580.                                 ((*te)->selEnd - (*te)->selStart)) > kMaxTELength )
  1581.                             AlertUser(eExceedPaste);
  1582.                         else
  1583.                         {
  1584.                             aHandle = (Handle) TEGetText(te);
  1585.                             oldSize = GetHandleSize(aHandle);
  1586.                             newSize = oldSize + scrapLength + kTESlop;
  1587.                             SetHandleSize(aHandle, newSize);
  1588.                             saveErr = MemError();
  1589.                             SetHandleSize(aHandle, oldSize);
  1590.                             if (saveErr != noErr)
  1591.                                 AlertUser(eNoSpacePaste);
  1592.                             else
  1593.                             {
  1594.                                 TEStylePaste(te);
  1595.                                 theDocument->modified = true;
  1596.                             };
  1597.                         };
  1598.                         break;
  1599.                     case iClear:
  1600.                         TEDelete(te);
  1601.                         theDocument->modified = true;
  1602.                         break;
  1603.                     case iSelectAll:
  1604.                         TESetSelect(0, (*te)->teLength, te);
  1605.                         break;
  1606.                 };
  1607.             if (menuItem != iCopy)
  1608.                 AdjustScrollbars(window, false);
  1609.             }
  1610.             break;
  1611.         case mFont:
  1612.             GetMenuItemText(GetMenuHandle(mFont), menuItem, theFontName);
  1613.             GetFNum(theFontName, &theFontID);
  1614.             theTextStyle.tsFont = theFontID;
  1615.             TESetStyle(doFont, &theTextStyle, true, te);
  1616.             if ((*te)->selEnd - (*te)->selStart > 0)
  1617.                 theDocument->modified = true;
  1618.             AdjustScrollbars(window, false);
  1619.             break;
  1620.         case mFontSize:
  1621.             switch (menuItem)
  1622.             {
  1623.                 case iNine:
  1624.                     theFontSize = 9;
  1625.                     break;
  1626.                 case iTen:
  1627.                     theFontSize = 10;
  1628.                     break;
  1629.                 case iTwelve:
  1630.                     theFontSize = 12;
  1631.                     break;
  1632.                 case iFourteen:
  1633.                     theFontSize = 14;
  1634.                     break;
  1635.                 case iEighteen:
  1636.                     theFontSize = 18;
  1637.                     break;
  1638.                 case iTwentyFour:
  1639.                     theFontSize = 24;
  1640.                     break;
  1641.             };
  1642.             theTextStyle.tsSize = theFontSize;
  1643.             TESetStyle(doSize, &theTextStyle, true, te);
  1644.             if ((*te)->selEnd - (*te)->selStart > 0)
  1645.                 theDocument->modified = true;
  1646.             AdjustScrollbars(window, false);
  1647.             break;
  1648.         case mStyle:
  1649.             switch (menuItem)
  1650.             {
  1651.                 case iPlain:
  1652.                     *((short *) &theTextStyle.tsFace) = 0; // see technote TE 16
  1653.                     theTextStyle.tsFace = normal;
  1654.                     break;
  1655.                 case iBold:
  1656.                     theTextStyle.tsFace = bold;
  1657.                     break;
  1658.                 case iItalic:
  1659.                     theTextStyle.tsFace = italic;
  1660.                     break;
  1661.                 case iUnderline:
  1662.                     theTextStyle.tsFace = underline;
  1663.                     break;
  1664.                 case iOutline:
  1665.                     theTextStyle.tsFace = outline;
  1666.                     break;
  1667.                 case iShadow:
  1668.                     theTextStyle.tsFace = shadow;
  1669.                     break;
  1670.             };
  1671.             if (menuItem == iPlain)
  1672.                 TESetStyle(doFace, &theTextStyle, true, te); // doToggle doesn't work for plain
  1673.             else
  1674.                 TESetStyle(doFace + doToggle, &theTextStyle, true, te);
  1675.             if ((*te)->selEnd - (*te)->selStart > 0)
  1676.                 theDocument->modified = true;
  1677.             AdjustScrollbars(window, false);
  1678.             break;
  1679.     };
  1680.     HiliteMenu(0);                    /* unhighlight what MenuSelect (or MenuKey) hilited */
  1681. }
  1682.  
  1683.  
  1684. /* Create a new document and window. */
  1685.  
  1686. void DoNew(void)
  1687. {
  1688.     Boolean        good;
  1689.     Ptr            storage;
  1690.     WindowPtr    window;
  1691.     Rect        destRect, viewRect;
  1692.     DocumentPeek doc;
  1693. #if qInline
  1694.     OSType        supportedInterfaces[1];
  1695. #endif // qInline
  1696.  
  1697.     storage = NewPtr(sizeof(DocumentRecord));
  1698.     if ( storage != nil ) {
  1699.         doc = (DocumentPeek) storage;
  1700.         doc->modified = false;
  1701. #if qInline
  1702.         doc->docTSMTERecHandle = nil;
  1703.         doc->docTSMDoc = nil;
  1704. #endif // qInline
  1705.         window = GetNewWindow(rDocWindow, storage, (WindowPtr) -1);
  1706.         if ( window != nil ) {
  1707.             gNumDocuments += 1;            /* this will be decremented when we call DoCloseWindow */
  1708.             good = false;
  1709.             SetPort(window);
  1710.             
  1711.             // on a Roman system, the default text size is 0; we need a real size to make the Size menu work
  1712.             TextSize(GetDefFontSize());
  1713.             GetTERect(window, &viewRect);
  1714.             destRect = viewRect;
  1715.             destRect.right = destRect.left + kMaxDocWidth;
  1716.             doc->docTE = TEStyleNew(&destRect, &viewRect);
  1717.             good = doc->docTE != nil;    /* if TENew succeeded, we have a good document */
  1718.             if ( good ) {                /* 1.02 - good document? — proceed */
  1719.                 TEAutoView(true, doc->docTE);
  1720.                 doc->docClick = (*doc->docTE)->clickLoop;
  1721. #if powerc
  1722.                 TESetClickLoop(gClickLoopUPP, doc->docTE);
  1723. #else
  1724.                 (*doc->docTE)->clickLoop = (TEClickLoopUPP) AsmClickLoopProc;
  1725. #endif
  1726.             }
  1727.             
  1728.             if ( good ) {                /* good document? — get scrollbars */
  1729.                 doc->docVScroll = GetNewControl(rVScroll, window);
  1730.                 good = (doc->docVScroll != nil);
  1731.             }
  1732.             if ( good) {
  1733.                 doc->docHScroll = GetNewControl(rHScroll, window);
  1734.                 good = (doc->docHScroll != nil);
  1735.             }
  1736. #if qInline
  1737.             if (good && gHasTSMTE)
  1738.             {
  1739.                 supportedInterfaces[0] = kTSMTEInterfaceType;
  1740.                 if (NewTSMDocument(1, supportedInterfaces, &doc->docTSMDoc,
  1741.                             (long) &doc->docTSMTERecHandle) == noErr)
  1742.                 {
  1743.                     TSMTERecPtr tsmteRecPtr = *(doc->docTSMTERecHandle);
  1744.                     
  1745.                     tsmteRecPtr->textH = doc->docTE;
  1746.                     tsmteRecPtr->preUpdateProc = gTSMTEPreUpdateUPP;
  1747.                     tsmteRecPtr->postUpdateProc = gTSMTEPostUpdateUPP;
  1748.                     tsmteRecPtr->updateFlag = kTSMTEAutoScroll;
  1749.                     tsmteRecPtr->refCon = (long) window;
  1750.                 }
  1751.                 else
  1752.                     good = false;
  1753.             };
  1754. #endif // qInline
  1755.  
  1756.             if ( good ) {                /* good? — adjust & draw the controls, draw the window */
  1757.                 /* false to AdjustScrollValues means musn’t redraw; technically, of course,
  1758.                 the window is hidden so it wouldn’t matter whether we called ShowControl or not. */
  1759.                 AdjustScrollValues(window, false);
  1760.                 ShowWindow(window);
  1761.                 AdjustMenus();
  1762.             } else {
  1763.                 DoCloseWindow(window);    /* otherwise regret we ever created it... */
  1764.                 AlertUser(eNoWindow);            /* and tell user */
  1765.             }
  1766.         } else
  1767.             DisposePtr(storage);            /* get rid of the storage if it is never used */
  1768.     }
  1769. } /*DoNew*/
  1770.  
  1771.  
  1772. /* Close a window. This handles desk accessory and application windows. */
  1773.  
  1774. /*    1.01 - At this point, if there was a document associated with a
  1775.     window, you could do any document saving processing if it is 'dirty'.
  1776.     DoCloseWindow would return true if the window actually closed, i.e.,
  1777.     the user didn’t cancel from a save dialog. This result is handy when
  1778.     the user quits an application, but then cancels the save of a document
  1779.     associated with a window. */
  1780.  
  1781. Boolean DoCloseWindow(WindowPtr window)
  1782. {
  1783.     DocumentPeek theDocument;
  1784.  
  1785.     if ( IsDAWindow(window) )
  1786.         CloseDeskAcc(((WindowPeek) window)->windowKind);
  1787.     else if ( IsDocumentWindow(window) ) {
  1788.         // ??? check modified flag whether document needs saving
  1789.         theDocument = (DocumentPeek) window;
  1790. #if qInline
  1791.         if (theDocument->docTSMDoc != nil)
  1792.         {
  1793.             (void) FixTSMDocument(theDocument->docTSMDoc);
  1794.             // DeleteTSMDocument might cause crash if we don't deactivate first, so...
  1795.             (void) DeactivateTSMDocument(theDocument->docTSMDoc);
  1796.             (void) DeleteTSMDocument(theDocument->docTSMDoc);
  1797.         };
  1798. #endif // qInline
  1799.         if (theDocument->docTE != nil)
  1800.             TEDispose(theDocument->docTE);
  1801.         /*    1.01 - We used to call DisposeWindow, but that was technically
  1802.             incorrect, even though we allocated storage for the window on
  1803.             the heap. We should instead call CloseWindow to have the structures
  1804.             taken care of and then dispose of the storage ourselves. */
  1805.         CloseWindow(window);
  1806.         DisposePtr((Ptr) window);
  1807.         gNumDocuments -= 1;
  1808.     }
  1809.     AdjustMenus();
  1810.     return true;
  1811. } /*DoCloseWindow*/
  1812.  
  1813.  
  1814. // PrintText prints the text in the edit record. It opens a printer port, calculates
  1815. // the number of lines per page (which may be different for each page depending on the
  1816. // text styles) and then calls TEUpdate for the page, scrolls a page and calls TEUpdate,
  1817. // etc.
  1818.  
  1819. static void PrintText(TEHandle theText)
  1820. {
  1821.     const short kMargin = 20; // page margins in pixels
  1822.     const Rect zeroRect = { 0, 0, 0, 0 };
  1823.     short totalLines;
  1824.     GrafPtr oldPort;
  1825.     Rect oldViewRect;
  1826.     Rect oldDestRect;
  1827.     Rect viewRect;
  1828.     Rect updateRect;
  1829.     Rect clipRect;
  1830.     short totalHeight;
  1831.     short currentLine;
  1832.     short scrollAmount;
  1833.     TPrStatus thePrinterStatus;
  1834.     Boolean printManagerIsOpen = false;
  1835.     Boolean userHasCancelled = false;
  1836.     short viewHeight;
  1837.     TPPrPort thePrinterPort;
  1838.     
  1839.     if (gPrinterRecord != nil)
  1840.     {
  1841.         PrOpen();
  1842.         if (PrJobDialog(gPrinterRecord))
  1843.         {
  1844.             GetPort(&oldPort);
  1845.             oldViewRect = (*theText)->viewRect;
  1846.             oldDestRect = (*theText)->destRect;
  1847.             thePrinterPort = PrOpenDoc(gPrinterRecord, nil, nil);
  1848.             printManagerIsOpen = (PrError() == noErr);
  1849.         };
  1850.     };
  1851.     
  1852.     if (printManagerIsOpen)
  1853.     {
  1854.         SetPort((GrafPtr) thePrinterPort);
  1855.         
  1856.         // re-wrap the text to fill the entire page minus margins
  1857.         viewRect = (*gPrinterRecord)->prInfo.rPage;
  1858.         InsetRect(&viewRect, kMargin, kMargin);
  1859.         (*theText)->inPort = (GrafPtr) thePrinterPort;
  1860.         (*theText)->destRect = viewRect;
  1861.         (*theText)->viewRect = viewRect;
  1862.         TECalText(theText);
  1863.         totalLines = (*theText)->nLines;
  1864.         totalHeight = TEGetHeight(totalLines, 0, theText);
  1865.         (*theText)->destRect.bottom = (*theText)->destRect.top + totalHeight;
  1866.         
  1867.         currentLine = 1;
  1868.         
  1869.         while ((!userHasCancelled) && (currentLine <= totalLines))
  1870.         {
  1871.             PrOpenPage(thePrinterPort, nil);
  1872.             scrollAmount = 0;
  1873.             clipRect = (*gPrinterRecord)->prInfo.rPage;
  1874.             ClipRect(&clipRect);
  1875.             
  1876.             viewHeight = (*theText)->viewRect.bottom - (*theText)->viewRect.top + 1;
  1877.             
  1878.             while (((scrollAmount + TEGetHeight(currentLine, currentLine, theText)) <= viewHeight)
  1879.                         && (currentLine <= totalLines))
  1880.             {
  1881.                 scrollAmount += TEGetHeight(currentLine, currentLine, theText);
  1882.                 currentLine++;
  1883.             };
  1884.             
  1885.             (*theText)->viewRect.bottom = scrollAmount + kMargin;
  1886.             TEDeactivate(theText); // avoid printing selections
  1887.             updateRect = (*theText)->viewRect;
  1888.             TEUpdate(&updateRect, theText);
  1889.             ClipRect(&zeroRect); // prevent TEScroll from redrawing the text
  1890.             TEScroll(0, -scrollAmount, theText); // scroll so we can print the next page
  1891.             (*theText)->viewRect.bottom = viewRect.bottom; // reset to full page;
  1892.             
  1893.             if (PrError() == iPrAbort)
  1894.                 userHasCancelled = true;
  1895.             PrClosePage(thePrinterPort);
  1896.         };
  1897.         
  1898.         PrCloseDoc(thePrinterPort);
  1899.  
  1900.         if ((*gPrinterRecord)->prJob.bJDocLoop == bSpoolLoop && PrError() == noErr)
  1901.             PrPicFile(gPrinterRecord, nil, nil, nil, &thePrinterStatus);
  1902.         PrClose();
  1903.         
  1904.         SetPort(oldPort);
  1905.         (*theText)->inPort = oldPort;
  1906.         (*theText)->viewRect = oldViewRect;
  1907.         (*theText)->destRect = oldDestRect;
  1908.         TECalText(theText);
  1909.         updateRect = (*theText)->viewRect;
  1910.         TEUpdate(&updateRect, theText);
  1911.     };
  1912. }
  1913.  
  1914. #if qAppleEvents
  1915.  
  1916. // handle the Quit menu command or Apple event by closing all windows, and
  1917. // setting gQuitting if successful.
  1918.  
  1919. static void PrepareToQuit(void)
  1920. {
  1921.     WindowPtr aWindow;
  1922.     
  1923.     gQuitting = true;
  1924.     aWindow = FrontWindow();
  1925.  
  1926.     while (gQuitting && (aWindow != nil))
  1927.     {
  1928.         gQuitting = DoCloseWindow(aWindow);
  1929.         aWindow = FrontWindow();
  1930.     };
  1931. }
  1932.  
  1933. #if qInline
  1934.  
  1935. static void ExitApplication(void)
  1936. {
  1937.     if (gHasTextServices)
  1938.         (void) CloseTSMAwareApplication();
  1939.  
  1940.     // set global fontForce flag so other apps don't get confused
  1941.     (void) SetScriptManagerVariable(smFontForce, gSavedFontForce);
  1942.  
  1943.     ExitToShell();
  1944. }
  1945.  
  1946. #endif // qInline
  1947. #else // qAppleEvents
  1948.  
  1949. // handle the Quit menu command by closing all windows, and quitting if successful.
  1950.  
  1951. static void Terminate(void)
  1952. {
  1953.     WindowPtr aWindow;
  1954.     Boolean closed;
  1955.     
  1956.     closed = true;
  1957.     aWindow = FrontWindow();
  1958.  
  1959.     while (closed && (aWindow != nil))
  1960.     {
  1961.         closed = DoCloseWindow(aWindow);
  1962.         aWindow = FrontWindow();
  1963.     };
  1964.  
  1965.     if (closed)
  1966.         ExitToShell(); // exit if no cancellation
  1967. }
  1968.  
  1969. #endif // qAppleEvents
  1970.  
  1971. /* Return a rectangle that is inset from the portRect by the size of
  1972.     the scrollbars and a little extra margin. */
  1973.  
  1974. void GetTERect(WindowPtr window, Rect *teRect)
  1975. {
  1976.     *teRect = window->portRect;
  1977.     InsetRect(teRect, kTextMargin, kTextMargin);    /* adjust for margin */
  1978.     teRect->bottom = teRect->bottom - 15;        /* and for the scrollbars */
  1979.     teRect->right = teRect->right - 15;
  1980. } /*GetTERect*/
  1981.  
  1982.  
  1983. /* Update the TERec's view rect so that it is the greatest multiple of
  1984.     the lineHeight that still fits in the old viewRect. */
  1985.  
  1986. void AdjustViewRect(TEHandle docTE)
  1987. {
  1988.     TEPtr        te;
  1989.     
  1990.     te = *docTE;
  1991.     te->viewRect.bottom = (((te->viewRect.bottom - te->viewRect.top) / te->lineHeight)
  1992.                             * te->lineHeight) + te->viewRect.top;
  1993. } /*AdjustViewRect*/
  1994.  
  1995.  
  1996. /* Scroll the TERec around to match up to the potentially updated scrollbar
  1997.     values. This is really useful when the window has been resized such that the
  1998.     scrollbars became inactive but the TERec was already scrolled. */
  1999.  
  2000. void AdjustTE(WindowPtr window)
  2001. {
  2002.     TEPtr        te;
  2003.     
  2004.     te = *((DocumentPeek)window)->docTE;
  2005.     TEScroll((te->viewRect.left - te->destRect.left) -
  2006.             GetControlValue(((DocumentPeek)window)->docHScroll),
  2007.             (te->viewRect.top - te->destRect.top) -
  2008.                 GetControlValue(((DocumentPeek)window)->docVScroll),
  2009.             ((DocumentPeek)window)->docTE);
  2010. } /*AdjustTE*/
  2011.  
  2012.  
  2013. /* Calculate the new control maximum value and current value, whether it is the horizontal or
  2014.     vertical scrollbar. The vertical max is calculated by comparing the number of lines to the
  2015.     vertical size of the viewRect. The horizontal max is calculated by comparing the maximum document
  2016.     width to the width of the viewRect. The current values are set by comparing the offset between
  2017.     the view and destination rects. If necessary and we canRedraw, have the control be re-drawn by
  2018.     calling ShowControl. */
  2019.  
  2020. // for styled text, we cannot rely on the line height being constant, so we
  2021. // have to use pixels instead of nLines
  2022.  
  2023. void AdjustHV(Boolean isVert, ControlHandle control, TEHandle docTE, Boolean canRedraw)
  2024. {
  2025.     short        value, max;
  2026.     short        oldValue, oldMax;
  2027.     
  2028.     oldValue = GetControlValue(control);
  2029.     oldMax = GetControlMaximum(control);
  2030.     if (isVert)
  2031.         max = TEGetHeight((*docTE)->nLines, 0, docTE) -
  2032.                     ((*docTE)->viewRect.bottom - (*docTE)->viewRect.top);
  2033.     else
  2034.         max = kMaxDocWidth - ((*docTE)->viewRect.right - (*docTE)->viewRect.left);
  2035.     if (max < 0)
  2036.         max = 0;
  2037.     SetControlMaximum(control, max);
  2038.     if (isVert)
  2039.         value = (*docTE)->viewRect.top - (*docTE)->destRect.top;
  2040.     else
  2041.         value = (*docTE)->viewRect.left - (*docTE)->destRect.left;
  2042.     
  2043.     if ( value < 0 )
  2044.         value = 0;
  2045.     else if ( value >  max ) value = max;
  2046.     
  2047.     SetControlValue(control, value);
  2048.     /* now redraw the control if it needs to be and can be */
  2049.     if ( canRedraw || (max != oldMax) || (value != oldValue) )
  2050.         ShowControl(control);
  2051. } /*AdjustHV*/
  2052.  
  2053.  
  2054. /* Simply call the common adjust routine for the vertical and horizontal scrollbars. */
  2055.  
  2056. void AdjustScrollValues(WindowPtr window, Boolean canRedraw)
  2057. {
  2058.     DocumentPeek doc;
  2059.     
  2060.     doc = (DocumentPeek)window;
  2061.     AdjustHV(true, doc->docVScroll, doc->docTE, canRedraw);
  2062.     AdjustHV(false, doc->docHScroll, doc->docTE, canRedraw);
  2063. } /*AdjustScrollValues*/
  2064.  
  2065.  
  2066. /*    Re-calculate the position and size of the viewRect and the scrollbars.
  2067.     kScrollTweek compensates for off-by-one requirements of the scrollbars
  2068.     to have borders coincide with the growbox. */
  2069.  
  2070. void AdjustScrollSizes(WindowPtr window)
  2071. {
  2072.     Rect        teRect;
  2073.     DocumentPeek doc;
  2074.     
  2075.     doc = (DocumentPeek) window;
  2076.     GetTERect(window, &teRect);                            /* start with TERect */
  2077.     (*doc->docTE)->viewRect = teRect;
  2078.     MoveControl(doc->docVScroll, window->portRect.right - kScrollbarAdjust, -1);
  2079.     SizeControl(doc->docVScroll, kScrollbarWidth, (window->portRect.bottom - 
  2080.                 window->portRect.top) - (kScrollbarAdjust - kScrollTweek));
  2081.     MoveControl(doc->docHScroll, -1, window->portRect.bottom - kScrollbarAdjust);
  2082.     SizeControl(doc->docHScroll, (window->portRect.right - 
  2083.                 window->portRect.left) - (kScrollbarAdjust - kScrollTweek),
  2084.                 kScrollbarWidth);
  2085. } /*AdjustScrollSizes*/
  2086.  
  2087.  
  2088. /* Turn off the controls by jamming a zero into their contrlVis fields (HideControl erases them
  2089.     and we don't want that). If the controls are to be resized as well, call the procedure to do that,
  2090.     then call the procedure to adjust the maximum and current values. Finally re-enable the controls
  2091.     by jamming a $FF in their contrlVis fields. */
  2092.  
  2093. void AdjustScrollbars(WindowPtr window, Boolean needsResize)
  2094. {
  2095.     DocumentPeek doc;
  2096.     
  2097.     doc = (DocumentPeek) window;
  2098.     /* First, turn visibility of scrollbars off so we won’t get unwanted redrawing */
  2099.     (*doc->docVScroll)->contrlVis = kControlInvisible;    /* turn them off */
  2100.     (*doc->docHScroll)->contrlVis = kControlInvisible;
  2101.     if ( needsResize )                                    /* move & size as needed */
  2102.         AdjustScrollSizes(window);
  2103.     AdjustScrollValues(window, needsResize);            /* fool with max and current value */
  2104.     /* Now, restore visibility in case we never had to ShowControl during adjustment */
  2105.     (*doc->docVScroll)->contrlVis = kControlVisible;    /* turn them on */
  2106.     (*doc->docHScroll)->contrlVis = kControlVisible;
  2107. } /* AdjustScrollbars */
  2108.  
  2109.  
  2110. // When the user selects text by dragging, TextEdit repeatedly calls a click loop routine which
  2111. // it gets from the TERecord's clikLoop field. TextEdit's default routine does some useful things,
  2112. // such as scrolling the text being selected, but it doesn't know about our scroll bars.
  2113. // Therefore, we replace the routine with one that calls both the old routine and an add-on routine
  2114. // which handles the scroll bars. Unfortunately, the way this works is very different for 68K and
  2115. // PowerPC. On 68K, we have to be aware that the original click loop routine has a register-based
  2116. // interface, so our replacement is easier to write in assembly. For PowerPC, we can let routine
  2117. // descriptors handle the argument conversions, and do everything in the C routine ClickLoopProc.
  2118.  
  2119. #if powerc
  2120. pascal Boolean ClickLoopProc(TEPtr pTE)
  2121. {
  2122.     CallTEClickLoopProc(GetOldClickLoop(), pTE);
  2123.     ClickLoopAddOn();
  2124.     return true;
  2125. }
  2126. #endif
  2127.  
  2128.  
  2129. // The ClickLoopAddOn routine handles the scroll bars during drag-scrolling.
  2130.  
  2131. pascal void ClickLoopAddOn(void)
  2132. {
  2133.     WindowPtr    window;
  2134.     RgnHandle    region;
  2135.     
  2136.     window = FrontWindow();
  2137.     region = NewRgn();
  2138.     GetClip(region);                    /* save clip */
  2139.     ClipRect(&window->portRect);
  2140.     AdjustScrollValues(window, true);    /* pass true for canRedraw */
  2141.     SetClip(region);                    /* restore clip */
  2142.     DisposeRgn(region);
  2143. }
  2144.  
  2145.  
  2146. // GetOldClickLoop returns the address of the default click loop routine that we put into the
  2147. // TERec when creating it.
  2148.  
  2149. pascal TEClickLoopUPP GetOldClickLoop(void)
  2150. {
  2151.     return ((DocumentPeek)FrontWindow())->docClick;
  2152. }
  2153.  
  2154.  
  2155. // Check whether a window is a document window created by the application.
  2156. // These windows have the windowKind userKind, so we can distinguish them from
  2157. // desk accessories, dialogs, and other windows.
  2158.  
  2159. Boolean IsDocumentWindow(WindowPtr window)
  2160. {
  2161.     return (window != nil) && (((WindowPeek) window)->windowKind == userKind);
  2162. }
  2163.  
  2164.  
  2165. // Check whether a window belongs to a desk accessory.
  2166. // These windows have negative windowKinds.
  2167.  
  2168. Boolean IsDAWindow(WindowPtr window)
  2169. {
  2170.     return (window != nil) && (((WindowPeek) window)->windowKind < 0);
  2171. }
  2172.  
  2173.  
  2174. /*    Display an alert that tells the user an error occurred, then exit the program.
  2175.     This routine is used as an ultimate bail-out for serious errors that prohibit
  2176.     the continuation of the application. Errors that do not require the termination
  2177.     of the application should be handled in a different manner. Error checking and
  2178.     reporting has a place even in the simplest application. The error number is used
  2179.     to index an 'STR#' resource so that a relevant message can be displayed. */
  2180.  
  2181. void AlertUser(short error)
  2182. {
  2183.     short        itemHit;
  2184.     Str255        message;
  2185.  
  2186.     SetCursor(&qd.arrow);
  2187.     /* type Str255 is an array in MPW 3 */
  2188.     GetIndString(message, rErrorStrings, error);
  2189.     ParamText(message, (ConstStr255Param) "", (ConstStr255Param) "", (ConstStr255Param) "");
  2190.     itemHit = Alert(rUserAlert, nil);
  2191. } /* AlertUser */
  2192.  
  2193. #if qAppleEvents
  2194.  
  2195. // Apple Event Support
  2196.  
  2197. static OSErr GotRequiredParameters(const AppleEvent *theAppleEvent)
  2198. {
  2199.     OSErr myErr;
  2200.     DescType returnedType;
  2201.     Size actualSize;
  2202.     
  2203.     myErr = AEGetAttributePtr(theAppleEvent, keyMissedKeywordAttr, typeWildCard, &returnedType,
  2204.                 nil, 0, &actualSize);
  2205.     if (myErr == errAEDescNotFound)
  2206.         return noErr;
  2207.     else if (myErr == noErr)
  2208.         return errAEParamMissed;
  2209.     else
  2210.         return myErr;
  2211. }
  2212.  
  2213. pascal OSErr HandleOAppEvent(const AppleEvent *theEvent, const AppleEvent *reply, long refCon)
  2214. {
  2215.     #pragma unused(reply, refCon)
  2216.     
  2217.     OSErr theError;
  2218.     
  2219.     theError = GotRequiredParameters(theEvent);
  2220.     if (theError == noErr)
  2221.         DoNew();
  2222.     return theError;
  2223. }
  2224.  
  2225. pascal OSErr HandleDocEvent(const AppleEvent *theEvent, const AppleEvent *reply, long refCon)
  2226. {
  2227.     #pragma unused(theEvent, reply, refCon)
  2228.     
  2229.     OSErr theError;
  2230.     AEDescList docList;
  2231.     long itemsInList;
  2232.     long index;
  2233.     AEKeyword keyword;
  2234.     DescType returnedType;
  2235.     FSSpec theFileSpec;
  2236.     Size actualSize;
  2237.     
  2238.     
  2239.     theError = AEGetParamDesc(theEvent, keyDirectObject, typeAEList,  &docList);
  2240.     if (theError == noErr)
  2241.     {
  2242.         theError = GotRequiredParameters(theEvent);
  2243.         if (theError == noErr)
  2244.         {
  2245.             theError = AECountItems(&docList, &itemsInList);
  2246.             if (theError == noErr)
  2247.             {
  2248.                 for (index = 1; index <= itemsInList; index++)
  2249.                 {
  2250.                     theError = AEGetNthPtr(&docList, index, typeFSS, &keyword, &returnedType,
  2251.                                 (Ptr) &theFileSpec, sizeof(theFileSpec), &actualSize);
  2252.                     if (theError == noErr)
  2253.                     {
  2254.                         if (refCon == kAEOpenDocuments)
  2255.                             // we don't open documents yet, but here's what it would look like:
  2256.                             // theError = OpenDocument(theFileSpec);
  2257.                             ;
  2258.                         else
  2259.                             // we don't print disk documents either (we can't read them),
  2260.                             // but here's what it would look like:
  2261.                             // theError = PrintDocument(theFileSpec);
  2262.                             ;
  2263.                     };
  2264.                 };
  2265.             };
  2266.         };
  2267.         (void) AEDisposeDesc(&docList);
  2268.     };
  2269.     return theError;
  2270. }
  2271.  
  2272. pascal OSErr HandleQuitEvent(const AppleEvent *theEvent, const AppleEvent *reply, long refCon)
  2273. {
  2274.     #pragma unused(reply, refCon)
  2275.     
  2276.     OSErr theError;
  2277.     
  2278.     theError = GotRequiredParameters(theEvent);
  2279.     if (theError == noErr)
  2280.     {
  2281.         PrepareToQuit();
  2282.         if (!gQuitting)
  2283.             theError = userCanceledErr;
  2284.     };
  2285.     return theError;
  2286. }
  2287.  
  2288. static OSErr InstallRequiredAppleEvents(void)
  2289. {
  2290.     OSErr result;
  2291.     
  2292.     gHandleOAppUPP = NewAEEventHandlerProc(HandleOAppEvent);
  2293.     FailNilUPP((UniversalProcPtr) gHandleOAppUPP);
  2294.     gHandleDocUPP = NewAEEventHandlerProc(HandleDocEvent);
  2295.     FailNilUPP((UniversalProcPtr) gHandleDocUPP);
  2296.     gHandleQuitUPP = NewAEEventHandlerProc(HandleQuitEvent);
  2297.     FailNilUPP((UniversalProcPtr) gHandleQuitUPP);
  2298.  
  2299.     result = AEInstallEventHandler(kCoreEventClass, kAEOpenApplication,
  2300.                 gHandleOAppUPP, 0, false);
  2301.     if (result == noErr)
  2302.         result = AEInstallEventHandler(kCoreEventClass, kAEOpenDocuments,
  2303.                     gHandleDocUPP, kAEOpenDocuments, false);
  2304.     if (result == noErr)
  2305.         result = AEInstallEventHandler(kCoreEventClass, kAEPrintDocuments,
  2306.                     gHandleDocUPP, kAEPrintDocuments, false);
  2307.     if (result == noErr)
  2308.         result = AEInstallEventHandler(kCoreEventClass, kAEQuitApplication,
  2309.                     gHandleQuitUPP, 0, false);
  2310.     return result;
  2311. }
  2312.  
  2313. #if qInline
  2314.  
  2315. // this TSMTEPreUpdateProc only works around a bug in TSMTE 1.0, which has
  2316. // been fixed in 1.1. For other possible uses, see technote TE 27.
  2317.  
  2318. static pascal void MyTSMTEPreUpdateProc(TEHandle textH, long refCon)
  2319. {
  2320.     #pragma unused(refCon)
  2321.  
  2322.     long response;
  2323.     ScriptCode keyboardScript;
  2324.     short mode;
  2325.     TextStyle theStyle;
  2326.     
  2327.     if ((Gestalt(gestaltTSMTEVersion, &response) == noErr) && (response == gestaltTSMTE1))
  2328.     {
  2329.         keyboardScript = GetScriptManagerVariable(smKeyScript);
  2330.         mode = doFont;
  2331.         if (!(TEContinuousStyle(&mode, &theStyle, textH) &&
  2332.                 FontToScript(theStyle.tsFont) == keyboardScript))
  2333.         {
  2334.             theStyle.tsFont = GetScriptVariable(keyboardScript, smScriptAppFond);
  2335.             TESetStyle(doFont, &theStyle, false, textH);
  2336.         };
  2337.     };
  2338. }
  2339.  
  2340. // this TSMTEPostUpdateProc makes sure that our scrollbars and scroll information
  2341. // are consistent with what TSMTE is doing. For other possible uses, see technote TE 27.
  2342.  
  2343. static pascal void MyTSMTEPostUpdateProc(TEHandle textH, long fixLen, long inputAreaStart,
  2344.             long inputAreaEnd, long pinStart, long pinEnd, long refCon)
  2345. {
  2346.     #pragma unused(textH, fixLen, inputAreaStart, inputAreaEnd, pinStart, pinEnd)
  2347.     
  2348.     AdjustScrollbars((WindowPtr) refCon, false);
  2349.     AdjustTE((WindowPtr) refCon);
  2350. }
  2351.  
  2352. #endif // qInline
  2353. #endif // qAppleEvents